Skip to content

fix(auth): preserve typed API error envelope when setup key verification fails#38

Open
lxcario wants to merge 1 commit into
TestSprite:mainfrom
lxcario:fix/setup-key-rejected-json-envelope
Open

fix(auth): preserve typed API error envelope when setup key verification fails#38
lxcario wants to merge 1 commit into
TestSprite:mainfrom
lxcario:fix/setup-key-rejected-json-envelope

Conversation

@lxcario

@lxcario lxcario commented Jun 25, 2026

Copy link
Copy Markdown

What

When setup --from-env --output json verifies an API key and the key is rejected (e.g. invalid or revoked), the JSON output is a bare {"error":"...string..."} instead of the typed envelope with code, nextAction, requestId, and details.

Why this matters

Machine consumers (coding agents, CI scripts) that JSON.parse(stderr) expect the typed envelope shape documented in DOCUMENTATION.md. A bare string error gives them no code to branch on, no nextAction to surface, and no requestId for support. Every other API error path in the CLI already emits the full typed envelope; this is the sole exception.

Reproduction

$ TESTSPRITE_API_KEY=sk-invalid testsprite setup --from-env --output json --yes --no-agent

# BEFORE (exit 3, bare string error):
{"error":"API key rejected by https://api.testsprite.com: API key is invalid or revoked. - did you mean to set TESTSPRITE_API_URL?"}

# AFTER (exit 3, full typed envelope):
{
  "error": {
    "code": "AUTH_INVALID",
    "message": "API key rejected by https://api.testsprite.com: API key is invalid or revoked. - did you mean to set TESTSPRITE_API_URL?",
    "nextAction": "API key is invalid or revoked. Generate a new one at https://www.testsprite.com/dashboard/settings/apikey.",
    "requestId": "cli_...",
    "details": { "reason": "malformed" }
  }
}

# Control: missing key (already correct, exit 5 with typed envelope):
$ testsprite setup --from-env --output json --yes --no-agent
{"error":{"code":"VALIDATION_ERROR","message":"TESTSPRITE_API_KEY is not set..."}}

Root cause

src/commands/auth.ts, runConfigure, line 157-168. The key-verification catch block wraps the original ApiError in a CLIError:

} catch (err) {
  ...
  throw new CLIError(
    `API key rejected by ${apiUrl}: ${message} - did you mean to set TESTSPRITE_API_URL?`,
    exitCode,
  );
}

CLIError has only message and exitCode. In index.ts, the CLIError path renders via output.error(err.message) which produces {"error":"...string..."} -- losing the full typed envelope that the ApiError path would have rendered.

Fix

When err is an ApiError, augment its .message with the endpoint context (so text-mode users still see which host rejected them), then re-throw it directly. The ApiError catch path in index.ts renders the full typed envelope. Non-ApiError throws (truly unexpected errors) still wrap in CLIError for a clean exit 3.

This mirrors how every other command path that catches and re-throws API errors already works (the error carries its own envelope; the CLI just sets exit code from it).

Tests

Added 1 regression test to src/commands/auth.test.ts asserting the thrown error is an ApiError with code: 'AUTH_INVALID', exitCode: 3, nextAction, and requestId intact. Fails on main before this fix; passes after.

  • Before (main): Tests 16 failed | 1359 passed | 72 skipped
  • After (this branch): Tests 16 failed | 1360 passed | 72 skipped (+1 new test)

The 16 failures are pre-existing and environment-specific (Windows path/line-ending), unrelated -- see #4.

Verification

  • npm test: same 16 pre-existing (environment-specific) failures as baseline, zero new
  • npm run typecheck: pass
  • npm run lint: pass

@zeshi-du

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@zeshi-du, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ebc05a26-16e3-42be-a98a-b629d911a36d

📥 Commits

Reviewing files that changed from the base of the PR and between 18f6e6e and d8d282e.

📒 Files selected for processing (2)
  • src/commands/auth.test.ts
  • src/commands/auth.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants